#!/bin/bash
# *************************************
# 功能: 主功能函数所依赖的功能函数
# 作者: 王树森
# 联系: wangshusen@sswang.com
# 版本: v0.1
# 日期: 2023-04-13
# *************************************

# 定制基础环境变量
expect_cmd='/usr/bin/expect'

# 定制ssh加速链接的配置
ssh_acceleration_link_logic(){
  # 定制变量
  local ssh_conf="$HOME/.ssh/config"

  # 定制加速配置
  cat > "${ssh_conf}"<<-eof
Host *
    Protocol 2
    ServerAliveInterval 60
    ServerAliveCountMax 30
    ControlMaster auto
    ControlPersist 10m
    ControlPath ~/.ssh/connect-%r@%h:%p.socket
eof
}

# 判断用户输入信息是否是数字或字符串
user_input_datatype_confirm(){
  # 接收参数
  local input_context="$1"

  # 数据格式判断
  if [[ "${input_context}" =~ ^[0-9]+$ ]]; then
    local num_type="is_num"
  elif [[ "${input_context}" =~ ^[0-9a-Z]$ ]]; then
    local num_type="is_string"
  else
    local num_type="other"
  fi

  echo "${num_type}"
}

# 生成用户定义的部署类型
user_define_xxx_type(){
  # 该功能允许接收2个或者3个输入选项
  # 接收参数
  local read_context="$1"
  local select_arg1="$2"
  local select_arg2="$3"
  if [ $# -gt 4 ]; then
    local select_arg3="$4"
    local default_context="$5"
  else
    local default_context="$4"
  fi
 
  # 确定用户输入的信息
  [ $# -gt 4 ] && local select_options="|${select_arg3}" || local select_options=""
  read -p "请输入${read_context}的方式(${select_arg1}-默认|${select_arg2}${select_options}): " u_input_context
  [ -e ${u_input_context} ] && u_input_context="${default_context}"
  if [ $# -gt 4 ]; then
    if [[ "${u_input_context}" != "${select_arg1}" && "${u_input_context}" != "${select_arg2}" && "${u_input_context}" != "${select_arg3}" ]]; then
      u_input_context="${default_context}"
    fi
  else
    if [[ "${u_input_context}" != "${select_arg1}" && "${u_input_context}" != "${select_arg2}" ]]; then
      u_input_context="${default_context}"
    fi
  fi

  echo "${u_input_context}"
}

# 用户定义基本信息状态函数
user_define_yes_no_interact(){
  # 接收参数
  local user_input="$1"
  local default_value="$2"

  # 定制用户是否需要重启主机
  read -t 10 -p "请确认${user_input}(yes|no): " user_input_status
  [ -z "${user_input_status}" ] && local user_input_status="${default_value}"
  
  # 返回用户输入的值
  echo "${user_input_status}"
}

# 定制获取远程主机操作系统类型函数
get_remote_os_type(){
  # 接收参数
  local host_type="$1"
  local remote_addr="$2"
 
  # 判断逻辑
  if [ "${host_type}" == "local" ]; then
    exec_cmd="grep -i ubuntu /etc/issue"
  elif [ "${host_type}" == "remote" ]; then
    exec_cmd="ssh ${login_user}@${remote_addr} grep -i ubuntu /etc/issue"
  else
    print::msg "all" "error" "请输入有效的主机类型(示例: local|remote)..."
  fi
  
  # 获取远程主机的操作系统类型
  ${exec_cmd} >/dev/null && local os_type="Ubuntu" || local os_type="CentOS"
  echo "${os_type}"
}

# 定制远程主机软件操作命令函数
get_remote_cmd_type(){
  # 接收参数
  local host_type="$1"
  local remote_addr="$2"

  # 判断逻辑
  local remote_os_type=$(get_remote_os_type "${host_type}" "${remote_addr}")
  
  # 定制远程主机软件操作命令
  if [ "${remote_os_type}" == "CentOS" ]; then
    local os_cmd_type="yum"
  else
    local os_cmd_type="apt"
  fi
  echo "${os_cmd_type}"
}

# 获取IP地址对应的主机名
get_remote_node_name(){
  # 接收参数
  local remote_addr="$1"
  local name_type="$2"

  # 获取逻辑
  case "${name_type}" in
    "long")
      local host_name=$(grep "${remote_addr}" "${host_file}" | awk '{print $2}');;
    "short")
      local host_name=$(grep "${remote_addr}" "${host_file}" | awk '{print $3}');;
    *)
      print::msg "console" "error" "请输入有效的主机名类型(long|short)..."
  esac
  echo "${host_name}"
}

# 获取远程主机的集群角色
get_remote_node_role(){
  # 接收参数
  local remote_addr="$1"
  
  # 判断逻辑
  local node_name=$(grep "${remote_addr}" "${host_file}" | awk '{print $NF}')
  if [[ "${node_name}" =~ .*node.*$ ]]; then
    local node_role="node"
  elif [[ "${node_name}" =~ .*master.*$ ]]; then
    local node_role="master"
  fi

  echo "${node_role}"
}

# 执行本地脚本文件
bash_local_script_file(){
  # 接收参数
  local script_file="$1"

  # 设定脚本运行选项
  [ "${shell_run_type}" == "debug" ] && local script_options="-x" || local script_options=""

  # 执行脚本
  if [ -f "${scripts_dir}/${script_file}" ]; then
    /bin/bash ${script_options} "${scripts_dir}/${script_file}"
  else
    print::msg "console" "error" "脚本 ${script_file} 文件不存在，请确认!!!"
  fi
}

# 创建目录逻辑函数
create_not_exist_dir_logic(){
  # 接收参数
  local dir_path="$1"

  # 创建目录
  [ -d "${dir_path}" ] || mkdir -p "${dir_path}"
}

# 删除目录功能函数
delete_exist_dir_logic(){
  # 接收参数
  local dir_path="$1"

  # 创建目录
  rm -rf "${dir_path}"
}


# 文件增删操作逻辑功能函数 - 待完善
file_create_delete_logic(){
  # 接收参数
  local file_ops="$1"
  local file_type="$2"
  local file_name="$3"

  # 文件操作命令获取
  if [ "${file_ops}" == "create" ];then
    if [ "${file_type}" == "dir" ];then
      local ops_cmd="[ -d ${file_name} ] && dir_file_status='is_exist' || mkdir -p ${file_name}"
    elif [ "${file_type}" == "file" ];then
      local file_path=$(dirname ${file_name})
      local ops_cmd="[ -d ${file_path} ] || mkdir -p ${file_path};  > ${file_name}"
    fi
  elif [ "${file_ops}" == "delete" ];then
    local ops_cmd="rm -rf  ${file_name}"
  fi
 
  # 返回根据条件确定的文件操作命令
  echo ${ops_cmd}
}

# 文集操作后信息输出
file_oper_msg_print(){
  # 接收参数
  local host_addr="$1" # local | ip_addr
  local file_ops="$2"  # create | delete
  local file_type="$3" # file | dir
  local file_name="$4" # 文件的完整路径

  [ "${host_addr}" == "local" ] && h_a_msg="本地主机"
  [[ "${host_addr}" =~ ${ip_addr_regex_rule} ]] && h_a_msg="远程主机 ${host_addr}"
  [ "${file_ops}" == "create" ] && f_o_msg="创建"
  [ "${file_ops}" == "delete" ] && f_o_msg="删除"
  [ "${file_type}" == "file" ] && f_t_msg="文件"
  [ "${file_type}" == "dir" ] && f_t_msg="目录"
  if [ "${dir_file_status}" == "is_exist" ]; then
    print::msg "all" "warning" "${file_name} 目录已存在，无需重复创建!!!"
  else
    print::msg "all" "success" "${h_a_msg} ${f_o_msg} ${file_name} ${f_t_msg}操作成功!!!"
  fi
}

# 文件操作逻辑功能函数-待完善
# 该函数的目的是，将所有的文件操作一网打尽
file_oper_logic(){
  # 接收参数
  local host_addr="$1" # local | ip_addr
  local file_ops="$2"  # create | delete
  local file_type="$3" # file | dir
  local file_name="$4" # 文件的完整路径

  # 执行动作
  if [ "${host_addr}" == "local" ]; then
    local_cmd=$(file_create_delete_logic "${file_ops}" "${file_type}" "${file_name}")
    eval ${local_cmd}
  elif [[ "${host_addr}" =~ ${ip_addr_regex_rule} ]]; then
    remote_cmd=$(file_create_delete_logic "${file_ops}" "${file_type}" "${file_name}")
    ssh "${login_user}@${host_addr}" "${remote_cmd}"
  fi
  
  file_oper_msg_print "${host_addr}" "${file_ops}" "${file_type}" "${file_name}"  
}

# 远程主机创建目录逻辑函数
remote_create_not_exist_dir_logic(){
  # 接收参数
  local remote_addr="$1"
  local dir_path="$2"

  # 创建远程主机目录
  ssh "${login_user}@${remote_addr}" "[ -d ${dir_path} ] || mkdir -p ${dir_path}"
}

# 远程主机删除目录功能函数
remote_delete_exist_dir_logic(){
  # 接收参数
  local remote_addr="$1"
  local dir_path="$2"

  # 删除远程主机目录
  ssh "${login_user}@${remote_addr}" "rm -rf ${dir_path}"
  print::msg "all" "success" "节点${remote_addr} 移除 ${dir_path} 文件成功!!!"
}

# 远程主机服务检测逻辑函数
remote_service_status_check(){
  # 接收参数
  local remote_addr="$1"
  local service_name="$2"

  # 检测服务环境是否正常
  local remote_service_status=$(ssh "${login_user}@${remote_addr}" \
                                "systemctl is-active ${service_name}")
  echo "${remote_service_status}"
}

# 远程主机服务初始化逻辑函数
remote_service_init(){
  # 接收参数
  local remote_addr="$1"
  local service_name="$2"

  # 服务初始化
  local init_cmd="systemctl daemon-reload; \
                  systemctl restart ${service_name}; \
                  systemctl enable ${service_name}"

  ssh "${login_user}@${remote_addr}" "${init_cmd}"

  # 检测远程服务状态
  local remote_service_status=$(remote_service_status_check "${remote_addr}" "${service_name}")
  if [ "${remote_service_status}" == "active" ]; then
    print::msg "all" "success" "节点${remote_addr} 服务 ${service_name} 初始化成功!!!"
  else
    print::msg "all" "error" "节点${remote_addr} 服务 ${service_name} 初始化异常!!!"
    return
  fi
}

# 远程主机服务停止功能函数
remote_service_remove(){
  # 接收参数
  local remote_addr="$1"
  local service_name="$2"

  # 服务关闭
  local service_remove_cmd="systemctl disable --now ${service_name}; \
                            systemctl stop ${service_name}"
  ssh "${login_user}@${remote_addr}" "${service_remove_cmd}"
}

# 远程主机软件清理功能函数
remote_softs_remove(){
  # 接收参数 
  local remote_addr="$1"
  local service_name="$2"

  # 软件移除
  local cmd_type=$(get_remote_cmd_type "remote" "${remote_addr}")
  local os_type=$(get_remote_os_type "remote" "${remote_addr}")
  if [ "${os_type}" == "Ubuntu" ]; then
    local softs_remove_cmd="${cmd_type} -y remove --purge ${service_name}; \
                            ${cmd_type} -y autoremove; ${cmd_type} clean"
  elif [ "${os_type}" == "CentOS" ]; then
    local softs_remove_cmd="${cmd_type} -y remove ${service_name}; ${cmd_type} autoremove"
  fi

  ssh "${login_user}@${remote_addr}" "${softs_remove_cmd}" 
  print::msg "all" "success" "节点${remote_addr} 移除 ${service_name} 软件成功!!!"
}

# 远程主机服务文件清理
remote_service_file_delete(){
  # 接收参数
  local remote_addr="$1"
  local file_name="$2"
  # 注意：这里的file_name最好给服务全称"xx.service"
  
  # 服务文件移除 
  # 考虑：服务文件的位置
  local service_dir="systemd/system"
  local file_list=$(eval echo {/etc,/usr/lib,/lib}/${service_dir}/${file_name})
  ssh "${login_user}@${remote_addr}" "rm -f ${file_list}"
  print::msg "all" "success" "节点${remote_addr} 移除 ${file_name} 服务文件成功!!!"
}

# 远程主机相关文件目录清理
remote_hosts_file_delete(){
  # 接收参数
  local remote_addr="$1"
  local target=$(echo $* | awk '{$1=null;print $0}')

  # 移除文件
  ssh "${login_user}@${remote_addr}" "rm -rf ${target}"
}

# 远程主机执行命令
remote_node_exec_cmd(){
  # 接收参数
  local remote_addr="$1"
  local cmd_context=$(echo $* | awk '{$1=null;print $0}')

  # 执行命令
  ssh "${login_user}@${remote_addr}" "${cmd_context}"
}

# 解压指定文件通用功能函数
untar_softs_base_func(){
  # 接收参数
  local tar_name="$1"
  local tar_path="$2"
  local linshi_dir="$3"
  
  # 判断文件是否存在
  local file_status=$(local_file_exist_status_check "${tar_path}")

  # 判断是否存在文件，若存在，则解压
  if [ "${file_status}" == "is_exist" ];then
    [ -d "${linshi_dir}" ] && rm -rf "${linshi_dir}"/* \
                                      || mkdir "${linshi_dir}"
    tar xf "${tar_path}" -C "${linshi_dir}"
    print::msg "all" "success" "指定版本的${tar_name}离线软件解压成功!!!"
  else
    print::msg "all" "warning" "没有指定版本的${tar_name}离线软件!!!"
    return
  fi
}

# 获取压缩包文件里面的文件列表
get_tar_file_dir_list(){
  # 接收参数
  local tar_path="$1"
  local dir_name="$2"
  
  # 获取文件列表
  local file_list=""
  local path_prefix="/"
  [[ "${tar_path}" =~ .*nerdctl.* ]] && local path_prefix="/usr/local/"
  for f in $(tar tf ${tar_path} ${dir_name} | grep -v "^${dir_name}/$"); do
    file_list="${path_prefix}${f} ${file_list}"
  done

  # 输出文件列表
  echo "${file_list}"
}

# 从github中获取软件的版本信息
get_soft_tags_from_github_logic(){
  # 接收参数
  local softs_tags_url="$1"
  
  # 获取tag信息
  echo "--------------------------------------------------------"

  # 临时参数
  if [[ "${softs_tags_url}" =~ .*tags$ ]]; then 
    local key_word="h2 data"
    curl --connect-timeout 10 -s "${softs_tags_url}" \
          | awk -F'>|<' "/${key_word}/{print \$(NF-4)}" \
          | grep -Ev 'rc|beta'
  elif [[ "${softs_tags_url}" =~ .*docker-ce.* ]]; then
    local key_word="docker-[0-9]"
    get_docker_tags_from_repo_logic "${softs_tags_url}" "${key_word}"    
  fi

  echo "--------------------------------------------------------"
}

# 格式化显示版本信息
print_docker_version_sub_list(){
  # 接收参数
  local softs_tags_url="$1"
  local main_ver="$2"

  # 获取版本列表
  local softs_list=$(curl --connect-timeout 10 -s ${softs_tags_url} \
                     | awk -F'>|<' "/${key_word}/{print \$(NF-4)}"  \
                     | grep ${main_ver} \
                     | awk -F'-|\.' '{print $2"."$3"."$4}' \
                     | sort -rnut'.' -k 3 | head -6)
  # 格式化输出
  local version_list=""
  for ver in ${softs_list}; do
    version_list="${ver} ${version_list}"
  done
  echo "${main_ver}系列: ${version_list}"
}

# 从软件源中获取软件的版本信息
get_docker_tags_from_repo_logic(){
  # 接收参数
  local softs_tags_url="$1"
  local key_word="$2"

  # 获取主版本列表
  local softs_main_ver_list=$(curl --connect-timeout 10 -s ${softs_tags_url} \
                              | awk -F'>|<' "/${key_word}/{print \$(NF-4)}"  \
                              | awk -F'-|\.' '{print $2"."$3}' \
                              | sort -nru | head -n10)

  # 格式化输出
  for main_ver in ${softs_main_ver_list}; do
    print_docker_version_sub_list "${softs_tags_url}" "${main_ver}"
  done
}

# 判断远程主机是否存活
remote_host_exist_check(){
  # 接收参数
  local remote_host="$1"

  # 检测远程主机的存活
  ping "${remote_host}" -c4  -i 0.3 -q  >/dev/null 2>&1
  [ $? -eq 0 ] && echo "is_exist" || echo "not_exist"
}

# 判断k8s集群是否正常运行
k8s_cluster_exist_check(){
  # 判断当前k8s集群是否正常运行
  ssh "${login_user}@${master1}" "kubectl cluster-info " >/dev/null 2>&1
  [ $? -eq 0 ] && echo "is_running" || echo "not_running"
}

# 判断文件是否存在
local_file_exist_status_check(){
  # 接收参数
  local filepath="$1"
  # local filename=$(basename "${filepath}")
  # local filedir=$(dirname "${filepath}")

  # 判断是否存在文件，若存在，则解压
  [ -f "${filepath}" ] && echo "is_exist" || echo "not_exist"
}

# expect在线部署逻辑函数
expect_install_online(){
  # 指定安装
  "${cmd_type}" install expect -y \
  && print::msg "all" "success" "expect软件安装成功!!!" \
  || (print::msg "all" "error" "expect软件安装失败!!! && exit")
}

# expect离线部署逻辑函数
expect_install_offline(){
  # 根据操作系统的区别来判断文件目录
  if [[ "${os_type}" == "CentOS" ]]; then
    if [ -f ${expect_centos_dir}/expect*.rpm ];then
      print::msg "all" "warning" "以离线线方式部署expect"
      "${cmd_type}" install -y ${expect_centos_dir}/*
    else
      print::msg "all" "error" "指定目录下没有expect离线软件，请提前准备!!!"
      exit
    fi
  else
    # 首先判断是否存离线文件，若存在则部署，否则下载文件后，提示安装
    if [ -f ${expect_ubuntu_dir}/expect*.deb ];then
      print::msg "all" "warning" "以离线线方式部署expect"
      dpkg -i ${expect_ubuntu_dir}/*
    else
      print::msg "all" "error" "指定目录下没有expect离线软件，请提前准备!!!"
      exit
    fi
  fi

}

# expect环境部署
expect_install(){
  # 判断expect是否安装
  if [ -f "${expect_cmd}" ]; then
    print::msg "all" "success" "expect环境已经部署完毕了!!!"
  else
    # 安装expect
    read -t 10 -p "请输入您准备以哪种方式安装expect(online|offline): " install_type
    # install_type=${install_type:-online}
    [ -n "${install_type}" ] && install_type="${default_deploy_type+${install_type}}" || install_type="${default_deploy_type}"
    if [ "${install_type}" == "online" ]; then
      print::msg "all" "warning" "以在线方式部署expect"
      expect_install_online
    else
      expect_install_offline
    fi
  fi  
}

# 判断当前用户的家目录函数
get_current_user_homedir(){
  # 获取当前用户
  local current_user=$(whoami)
  
  # 获取当前用户家目录
  [ "${current_user}" == "root" ] && local user_dir='/root' \
                                  || local user_dir="/home/${current_user}"

  # 返回信息
  echo "${user_dir}"
}

# ssh认证记录的生成函数
sshkey_auth_file_create(){
  # 接收参数
  local user_dir="$1"

  # 生成ssh秘钥对儿
  ssh-keygen -t rsa  -P "" -f "${user_dir}/.ssh/id_rsa"
}

# ssh秘钥生成
sshkey_create(){
  # 获取当前用户家目录
  local user_dir=$(get_current_user_homedir)

  # 是否重置ssh现有的秘钥对儿
  if [ "${k8s_cluster_create_reset_ssh}" == "yes" ]; then
    # 清理历史ssh记录信息
    [ -d "${user_dir}/.ssh" ] && rm -rf "${user_dir}/.ssh"
    # 生成ssh秘钥对儿
    sshkey_auth_file_create "${user_dir}"
    # 生成配套的known_hosts空文件
    > "${user_dir}/.ssh/known_hosts"
    print::msg "all" "success" "ssh秘钥对儿环境重置完毕!!!"
  elif [ "${k8s_cluster_create_reset_ssh}" == "no" ]; then
    if [ -d "${user_dir}/.ssh" ]; then
      # 存在历史ssh记录信息
      print::msg "all" "warning" "ssh文件信息存在，直接重用即可!!!"
    else
      # 不存在历史ssh记录信息
      # 生成ssh秘钥对儿
      sshkey_auth_file_create "${user_dir}"
      print::msg "all" "success" "ssh秘钥对儿环境初始化完毕!!!"
    fi
  fi

  # 部署服务器的ssh加速配置
  ssh_acceleration_link_logic
}

# 同步conf/hosts 和 /etc/hosts 主机解析记录-非初始化场景
# 注意：该函数作用范围，仅限于手工直接进行 ssh免密认证
sync_conf_etc_hosts_record(){
  # 获取conf/hosts文件的IP地址信息
  local conf_hosts_list=$(grep 'kuber' "${conf_dir}/${host_name}" | awk '{print $1}')

  # 校验conf/hosts的IP记录是否在/etc/hosts 文件里面存在
  for ip_record in ${conf_hosts_list}
  do
    local hosts_record_check_status=$(hosts_record_exist_check "${ip_record}")
    # 如果IP记录在 /etc/hosts 文件里面不存在，则增加记录
    if [ "${hosts_record_check_status}" == "not_exist" ];then
      local hostname_short=$(grep "${ip_record}" "${conf_dir}/${host_name}" | awk '{print $NF}')
      sync_hosts_add_record_use_hostname_logic "${ip_record}" "${hostname_short}"
    fi
  done
}

# 获取主机名解析记录的位置
get_hosts_record_location(){
  # 接收参数
  local arg_key="$1"
  
  # 获取位置
  local key_location=$(grep -n "${arg_key}" "${host_file}" | tail -1 | awk -F":" '{print $1}')
  
  echo "${key_location}"
}

# 在/etc/hosts文件中指定位置增加记录
hosts_file_add_record_logic(){
  # 接收参数
  local record_location="$1"
  local ip_addr="$2"
  local host_name="$3"

  # 增加主机名解析记录
  local new_host_record="${ip_addr} ${host_name}.sswang.com ${host_name}"
  sed -i "${record_location}a${new_host_record}" "${host_file}"
}

# 专属同步conf/hosts 和 /etc/hosts 主机解析记录的逻辑函数
sync_hosts_add_record_use_hostname_logic(){
  # 接收参数
  local ip_addr="$1"
  local host_name="$2"

  # 增加主机名解析记录
  local hosts_none_add_location=$(get_hosts_record_location "127")
  local hosts_master_add_location=$(get_hosts_record_location "master")
  local hosts_node_add_location=$(get_hosts_record_location "node")

  [ -e ${hosts_master_add_location} ] && local hosts_master_add_location="${hosts_none_add_location}"
  [ -e ${hosts_node_add_location} ] && local hosts_node_add_location="${hosts_none_add_location}"
  
  # 增加hosts记录
  if [[ "${host_name}" =~ .*master1.* ]];then
    # master1 记录增加
    hosts_file_add_record_logic "${hosts_none_add_location}" "${ip_addr}" "${host_name}"
  elif [[ "${host_name}" =~ .*master.* ]];then
    # 其他master记录增加
    hosts_file_add_record_logic "${hosts_master_add_location}" "${ip_addr}" "${host_name}"
  elif [[ "${host_name}" =~ .*node1.* ]];then
    # node1记录增加
    hosts_file_add_record_logic "${hosts_master_add_location}" "${ip_addr}" "${host_name}"
  elif [[ "${host_name}" =~ .*node.* ]];then
    # 其他node记录增加
    hosts_file_add_record_logic "${hosts_node_add_location}" "${ip_addr}" "${host_name}"
  else
    # 其他记录增加
    local new_host_record="${ip_addr} ${host_name}.sswang.com ${host_name}"
    echo "${new_host_record}" >> "${host_file}"
  fi
}

# hosts文件生成-初始化场景
hosts_create(){
  # 优化1：考虑创建hosts文件时的旧有记录处理
  for i in $(awk '/kuber/{print $1}' "${conf_dir}/${host_name}");do
    sed -i "/$i/d" "${host_file}"
  done
  # 增加当前集群的节点主机名解析记录
  awk '$0~"kubernetes-"{print $1,$2".sswang.com",$2}' "${conf_dir}/${host_name}" >> "${host_file}"
  print::msg "all" "success" "hosts文件创建完毕!!!"
}

# 判断主机条目是否存在
hosts_record_exist_check(){
  # 接收参数
  local arg_key="$1"
  
  # 判断逻辑
  grep "${arg_key}" "${host_file}" >>/dev/null && echo "is_exist" || echo "not_exist"
}

# 基于IP方式自动设定hosts新记录的主机名
use_ipaddr_auto_create_hostname(){
  # 接收参数
  local ip_addr="$1"
  
  # 确保记录不存在
  local host_record_status=$(hosts_record_exist_check "${ip_addr}")
  if [ "${host_record_status}" == "not_exist" ]; then
    # 定制标准格式的主机名解析记录格式
    local record_num=$(grep node "${host_file}" | tail -1 | awk -F'de' '{print $NF}')
    let record_new_num=$record_num+1
    local record_hostname="kubernetes-node${record_new_num}"
    
    # 增加主机名解析记录
    hosts_add_record_use_hostname "${ip_addr}" "${record_hostname}"
  else
    local record_hostname=$(grep "${ip_addr}" "${host_file}" | awk '{print $NF}')
  fi
  # 返回主机名解析记录
  echo "${record_hostname}"
}

# 
# 基于hostname方式增加单条hosts新记录
# 注意：该函数作用的是未知IP地址
hosts_add_record_use_hostname(){
  # 接收参数
  local ip_addr="$1"
  local host_name="$2"
  
  # 增加主机名解析记录
  local new_host_record="${ip_addr} ${host_name}.sswang.com ${host_name}"
  local hosts_add_location=$(grep -n node "${host_file}" | tail -1 | awk -F":" '{print $1}')

  # 为hosts增加新的条目
  sed -i "${hosts_add_location}a${new_host_record}" "${host_file}"
}

# bug修复: 新增ip地址准备工作函数
check_ipaddr(){
  # 接收参数
  local ip="$1"
  # 判断ip地址是否存在
  local ip_status=$(grep ${ip} ${host_file} >/dev/null && echo "is_exist" || echo "no_exist")
  if [ "${ip_status}" == "no_exist" ]; then
    # 定制 短域名
    local node_num=$(awk -F"node" '/node/{print $NF}' "${conf_dir}/${host_name}" | tail -1)
    let new_node_num=node_num+1
    local new_node_name="kubernetes-node${new_node_num}"

    # 更新 conf/hosts 文件
    # 在最后一个node节点的下一行增加
    sed -i "/node${node_num}/a${ip} ${new_node_name}" "${conf_dir}/${host_name}"

    # 更新 /etc/hosts 文件
    sed -i "/node${node_num}/a${ip} ${new_node_name}.sswang.com ${new_node_name}" "${host_file}"
  fi
}

# 检测节点ssh秘钥是否存在
sshkey_auth_exist_check(){
  # 接收参数
  local remote_addr="$1"

  # 检测节点认证记录是否存在
  local host_status=$(ssh-keygen -l -F ${remote_addr} >/dev/null && echo "is_exist" || echo "no_exist")
  echo "${host_status}"
}

# 删除指定节点的ssh认证秘钥
sshkey_auth_exist_delete(){
  # 接收参数
  local remote_addr="$1"
  
  # 检测主机ssh秘钥记录
  local host_status=$(sshkey_auth_exist_check "${remote_addr}")
  if [ "${host_status}" == "is_exist" ]; then
    # 清理已存在主机记录
    ssh-keygen -f "/root/.ssh/known_hosts" -R "${remote_addr}"
    print::msg "all" "success" "主机 $remote_addr ssh认证信息已删除!!!"
  else
    # echo -e "\e[33m主机 $remote_addr ssh认证信息不存在!!!\e[0m"
    print::msg "all" "error" "主机 $remote_addr ssh认证信息不存在!!!"
  fi
}

# ssh 跨主机免密码认证
sshkey_auth_func(){
  # 接收参数
  local ip_list="$*"
  # 执行跨主机免密码操作
  local cmd="ssh-copy-id -i $HOME/.ssh/id_rsa.pub "
  for ip in ${ip_list}; do
    # bug修复: 新增ip地址时跨主机免密码认证异常
    check_ipaddr "${ip}"
    # 主机名解析记录，在多种场景下，长短主机名都可能用到，所以这三个都做认证
    for addr in $(grep $ip ${host_file}); do
      sshkey_auth_exist_delete "${addr}"
      # 借助循环构造多格式主机目标
      local target="${login_user}@${addr}"
      expect_autoauth_func "${cmd}" "${target}"
      print::msg "all" "success" "主机 $addr 已经实现跨主机免密码认证!!!"
    done
  done
}

# expect 实现自动化命令执行
expect_autoauth_func(){
  # 接收参数
  local remote_cmd="$*"
  # expect的全程干预
  /usr/bin/expect -c "
    spawn ${remote_cmd}
    expect {
      \"yes/no\" {send \"yes\r\"; exp_continue}
      \"*password*\" {send \"${login_pass}\r\"; exp_continue}
      \"*password*\" {send \"${login_pass}\r\"}
    }"
}

# 远程传输文件
scp_file_bak(){
  # 接收参数
  local ip_list="$*"
  # 远程传输文件
  for ip in ${ip_list}; do
   scp "${host_file}" "${login_user}@${ip}:${host_file}"
  done
}

# 远程传输具体的文件
scp_file(){
  # 接收参数
  local target_dir=$(echo $* | awk '{print $NF}')
  local source_file=$(echo $* | awk '{print $(NF-1)}')
  local ip_list=$(echo $* | awk '{$NF=null;$(NF-1)=null;print $0}')
  local filename=$(basename ${source_file})
  local target_file="${target_dir}/${filename}"
  # 远程传输文件
  for ip in ${ip_list}; do
    scp "${source_file}" "${login_user}@${ip}:${target_file}"
  done
}

# 远程传输目录文件
scp_dir(){
  # 接收参数
  local target_dir=$(echo $* | awk '{print $NF}')
  local source_dir=$(echo $* | awk '{print $(NF-1)}')
  local ip_list=$(echo $* | awk '{$NF=null;$(NF-1)=null;print $0}')

  # 远程传输目录
  for ip in ${ip_list}; do
    scp -r "${source_dir}" "${login_user}@${ip}:${target_dir}"
  done
}

# 获取目录所有文件功能函数
get_dir_all_file_list(){
  # 接收参数
  local dir_name="$1"

  # 循环遍历所有的嵌套目录文件
  for file in $(ls ${dir_name}); do
    if [ -d "${dir_name}/$file" ]; then
      get_dir_all_file_list "${dir_name}/${file}"
    elif [ -f "${dir_name}/$file" ]; then
     dir_file_list="${dir_file_list} ${dir_name}/${file}"
    fi
  done
}

# 文件传输判断功能函数
scp_file_with_check(){
  # 接收参数
  local h_addr="$1"
  local s_file="$2"
  local t_dir="$3"

  local f_name=$(echo "${s_file##*/}")
  local t_file="${t_dir}/${f_name}"

  # 判断本地文件和远程文件是否一致
  local l_md5sum=$(md5sum "${s_file}" | awk '{print $1}')
  local r_md5sum=$(ssh "${login_user}@${h_addr}" "[ -f ${t_file} ] \
                        && md5sum ${t_file}" | awk '{print $1}')
  if [ "${l_md5sum}" == "${r_md5sum}" ];then
    print::msg "all" "warning" "文件 ${f_name} 在 ${h_addr} 已存在，无需重复传输!!!"
  else
    remote_create_not_exist_dir_logic "${h_addr}" "${t_dir}"
    scp "${s_file}" "${login_user}@${h_addr}:${t_file}"
  fi
}

# 远程传输文件通用功能函数
scp_file_base_func(){
  # 接收参数
  local tdir=$(echo $* | awk '{print $NF}')
  local sfile=$(echo $* | awk '{print $(NF-1)}')
  local ip_list=$(echo $* | awk '{$NF=null;$(NF-1)=null;print $0}')

  # 判断文件类型
  [ -d "${sfile}" ] && local file_type="dir"
  [ -f "${sfile}" ] && local file_type="file"

  # 传输远程文件
  for ip in ${ip_list}; do
    if [ "${file_type}" = "file" ]; then
      scp_file_with_check "${ip}" "${sfile}" "${tdir}"
    elif [ "${file_type}" == "dir" ]; then
      # 定制变量
      local dir_file_list=""
      get_dir_all_file_list "${sfile}"
      for file in ${dir_file_list}; do
        local sub_path=$(dirname $(echo "${file##*${sfile}/}"))
        local t_dir="${tdir}/${sub_path}"
        scp_file_with_check "${ip}" "${file}" "${t_dir}"
      done
    fi
  done
}

# 传递hosts文件
scp_hosts_file(){
  # 接收参数
  local target_dir=$(echo $* | awk '{print $NF}')
  local source_file=$(echo $* | awk '{print $(NF-1)}')
  local ip_list=$(echo $* | awk '{$NF=null;$(NF-1)=null;print $0}')
  local filename=$(basename ${source_file})
  local target_file="${target_dir}/${filename}"

  # 检测文件内容是否一致
  for ip in ${ip_list}; do
    # 获取远程主机的hosts文件内容
    ssh "${login_user}@${ip}" "cat ${target_file}" > "/tmp/${host_name}"
    # 对比文件内容，确定是否拷贝文件
    host_context_status=$(diff ${target_file} /tmp/${host_name} >/dev/null \
                          && echo "same" || echo "unsame")
    if [ "${host_context_status}" == "same" ]; then
      print::msg "all" "warning" "主机 ${ip} hosts文件已同步，无需更新!!!"
    else
      scp_file ${ip} "${host_file}" "${host_target_dir}"
    fi
  done
}

# 跨主机设定主机名
set_hostname(){
  # 接收参数
  local ip_list="$*"
  # 远程执行命令
  for ip in ${ip_list}; do
    local remote_host="${login_user}@${ip}"
    local hostname=$(grep ${ip} ${host_file} | awk '{print $NF}')
    ssh ${remote_host} "hostnamectl set-hostname ${hostname}"
  done
}

# 生成ip列表
create_ip_list(){
  # 接收参数
  local ip_net="$1"
  local ip_tail="$2"
  local ip_list=""
  # 生成ip列表
  for i in $(eval echo ${ip_tail}); do
    ip_addr=$(echo -n "${ip_net}.${i} ")
    ip_list="${ip_list}${ip_addr}"
  done
  echo "${ip_list}"
}

# 生成用户定义的主机地址列表
user_define_create_ip_list(){
  # 接收参数
  local read_context="$1"

  # 定制生成IP地址列表
  read -p "请输入您要执行${read_context}的主机列表,只需要ip最后一位(示例: {12..20}): " num_list
  ip_list=$(create_ip_list "${target_net}" "${num_list}")

  echo "${ip_list}"
}

# 软件源同步
repo_update(){
  # 接收参数
  local ip_list="$*"

  # 远程执行更新命令
  for ip in ${ip_list}; do
    print::msg "all" "warning" "主机 ${ip} 软件源开始同步，时间可能有些长，请等待..."
    remote_os_status=$(ssh "${login_user}@${ip}" "grep -i ubuntu /etc/issue")
    [ -n "${remote_os_status}" ] && local os_type="Ubuntu" || local os_type="CentOS"
    if [ "${os_type}" == "CentOS" ]; then
      ssh "${login_user}@${ip}" "yum makecache fast"
    else
      ssh "${login_user}@${ip}" "rm -rf /var/lib/dpkg/lock*; apt update"
    fi
    print::msg "all" "success" "主机 ${ip} 软件源更新完毕!!!"
  done
}

# 定制CentOS 软件源函数
k8s_centos_repo(){
  # 定制centos环境软件源
  [ -f "/tmp/${centos_repo_file}" ] && rm -f "/tmp/${centos_repo_file}"
  cat > "/tmp/${centos_repo_file}" <<-eof
[kubernetes]
name=Kubernetes
baseurl=https://${k8s_sources_repo_addr}/kubernetes/yum/repos/kubernetes-el7-\$basearch
enabled=1
gpgcheck=0
eof
}

k8s_centos_repo_update(){
  # 接收参数
  local k8s_ver_num="$1"

  # 生成配套的软件源文件
  for i in $(eval echo {28..$k8s_ver_num}); do
    cat >> "/tmp/${centos_repo_file}" <<-eof
[kubernetes-1.${i}]
name=Kubernetes-1.${i}
baseurl=https://${k8s_sources_repo_addr}/kubernetes-new/core/stable/v1.${i}/rpm/
enabled=1
gpgcheck=0
eof
  done
}

# 定制Ubuntu 软件源函数
k8s_ubuntu_repo(){
  # 定制ubuntu环境软件源
  [ -f "/tmp/${ubuntu_repo_file}" ] && rm -f "/tmp/${ubuntu_repo_file}"
  cat >"/tmp/${ubuntu_repo_file}"<<-eof
deb https://${k8s_sources_repo_addr}/kubernetes/apt kubernetes-xenial main
eof
}

k8s_ubuntu_repo_update(){
  # 接收参数
  local k8s_ver_num="$1"

  # 生成配套的软件源文件
  for i in $(eval echo {28..$k8s_ver_num}); do
    cat >> "/tmp/${ubuntu_repo_file}"<<-eof
deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://${k8s_sources_repo_addr}/kubernetes-new/core/stable/v1.${i}/deb/ /
eof
  done
}

# 获取当前k8s最新的主版本数字
get_k8s_current_main_subnum(){
  # 获取最新的k8s最新的主版本数字
  local k8s_ver_num=$(curl https://github.com/kubernetes/kubernetes/tags --max-time 10 -s | grep -vE 'alpha|-rc|-beta' | awk -F'.' '/se v/{print $2}' | sort -r | head -1)

  # 如果网络异常，则使用默认属性的指定版本号
  [ -z $k8s_ver_num ] && local k8s_ver_num=$(echo ${k8s_version} | awk -F'.' '{print $2}')
 
  # 返回k8s主版本数字
  echo "${k8s_ver_num}"
}

# 定制k8s软件源创建的逻辑函数
k8s_create_repo_logic(){
  # 基础k8s软件源文件-【1.28-】
  k8s_ubuntu_repo
  k8s_centos_repo

  # 获取当前k8s的主版本号
  local k8s_ver_num=$(get_k8s_current_main_subnum)

  # 扩充软件源信息
  if [ ${k8s_ver_num} -ge 28 ]; then
    # 进阶k8s软件源文件-【1.28+】
    k8s_centos_repo_update "${k8s_ver_num}"
    k8s_ubuntu_repo_update "${k8s_ver_num}"
  fi
}

# 部署主机使用k8s软件源
deploy_host_apply_k8s_repo(){
  # 接收参数
  local k8s_version="$1"
  local k8s_ver_num=$(echo "${k8s_version%.*}")
	local k8s_main_ver_num=$(echo "${k8s_version}" | awk -F'.' '{print $2}')

  # 源秘钥信息获取
  local keyring_dir="/etc/apt/keyrings"
  local keyring_file="${keyring_dir}/kubernetes-apt-keyring.gpg"
  local keyring_new_url="https://${k8s_sources_repo_addr}/kubernetes-new/core/stable/v${k8s_ver_num}/deb/Release.key"
  local keyring_old_url="https://${k8s_sources_repo_addr}/kubernetes/apt/doc/apt-key.gpg"

  # 获取当前主机的类型
  if [ "${os_type}" == "Ubuntu" ]; then
    # 源文件复制
    cp "/tmp/${ubuntu_repo_file}" "${ubuntu_repo_dir}/${ubuntu_repo_file}"
    # 使用旧版本的keyring
    curl -s "${keyring_old_url}" | apt-key add -
    if [ "${k8s_main_ver_num}" -ge "28" ]; then
      # 使用新版本的keyring
      [ -d "${keyring_dir}" ] || mkdir -p "${keyring_dir}"
      [ -f "${keyring_file}" ] && rm -f "${keyring_file}"
      curl -fsSL ${keyring_new_url} | gpg --dearmor -o "${keyring_file}"
    fi
    /usr/bin/apt update -y
  elif [ "${os_type}" == "CentOS" ]; then
    cp "/tmp/${centos_repo_file}" "${centos_repo_dir}/${centos_repo_file}"
    /usr/bin/yum makecache fast
  fi
}

# 输出符号
echo_tag(){
  # 常见方法：
  # echo {1..30} | sed 's/[0-9]/=/g' | sed 's/ //g' 
  # seq -s "=" 100 | sed 's/[0-9]//g'
  # echo | awk '{for(i=1;i<=100;i++){printf "="}}{printf "\n"}'

  # 接收参数
  tag="$1"
  num="$2"
  
  # 输出指定长度的内容
  seq -s "${tag}" ${num} | sed 's/[0-9]//g'
}

# 输出绝对值函数
echo_abs(){
  # 接收参数
  local num="$1"
  if (($num>=0));then
    # 如果是正数，则直接输出
    echo $num
  else
    # 如果是负数，则输出正数
    echo $((-$num));
  fi  
}

# 定制旋转函数
waiting(){
  # 接收参数
  local end_time="$1"
  # 定制初始参数
  time=0
  while [ $time -lt "$end_time" ]; do
  # 开始循环
    for tag in "\\" "|" "/" "-"; do
      # 循环遍历符号
      printf "%c\r" "$tag"
      sleep 0.4
    done
    # 结束(-10s)
    let time+=1
  done
}

# 定制一键通用基础环境功能函数
one_key_base_env(){
  # 功能逻辑
  print::msg "all" "warning" "开始执行基本环境部署..."
  expect_install
  sshkey_create
  hosts_create
  print::msg "all" "warning" "开始执行跨主机免密码认证..."
  sshkey_auth_func "${all_node_list}" "localhost"
  print::msg "all" "warning" "开始执行同步集群hosts..."
  scp_hosts_file ${all_node_list} "${host_file}" "${host_target_dir}"
  print::msg "all" "warning" "开始执行设定集群主机名..."
  set_hostname ${all_node_list}
  print::msg "all" "warning" "开始执行更新软件源..."
  repo_update "${all_node_list}"
}
